/*
 * Inline Form Validation Engine 2.6.2, jQuery plugin
 * 
 * Copyright(c) 2010, Cedric Dugas http://www.position-absolute.com
 * 
 * 2.0 Rewrite by Olivier Refalo http://www.crionics.com
 * 
 * Form validation engine allowing custom regex rules to be added. Licensed
 * under the MIT License
 */
(function($) {

	"use strict";

	var methods = {

		/**
		 * Kind of the constructor, called before any action
		 * 
		 * @param {Map}
		 *            user options
		 */
		init : function(options) {
			var form = this;
			if (!form.data('jqv') || form.data('jqv') == null) {
				options = methods._saveOptions(form, options);
				// bind all formError elements to close on click
				$(document).on("click", ".formError", function() {
							$(this).fadeOut(150, function() {
										// remove prompt once invisible
										$(this).parent('.formErrorOuter')
												.remove();
										$(this).remove();
									});
						});
			}
			return this;
		},
		/**
		 * Attachs jQuery.validationEngine to form.submit and field.blur events
		 * Takes an optional params: a list of options ie.
		 * jQuery("#formID1").validationEngine('attach', {promptPosition :
		 * "centerRight"});
		 */
		attach : function(userOptions) {

			var form = this;
			var options;

			if (userOptions)
				options = methods._saveOptions(form, userOptions);
			else
				options = form.data('jqv');

			options.validateAttribute = (form
					.find("[data-validation-engine*=validate]").length)
					? "data-validation-engine"
					: "class";
			if (options.binded) {

				// delegate fields
				form
						.on(
								options.validationEventTrigger,
								"["
										+ options.validateAttribute
										+ "*=validate]:not([type=checkbox]):not([type=radio]):not(.datepicker)",
								methods._onFieldEvent);
				form.on("click", "[" + options.validateAttribute
								+ "*=validate][type=checkbox],["
								+ options.validateAttribute
								+ "*=validate][type=radio]",
						methods._onFieldEvent);
				form.on(options.validationEventTrigger, "["
								+ options.validateAttribute
								+ "*=validate][class*=datepicker]", {
							"delay" : 300
						}, methods._onFieldEvent);
			}
			if (options.autoPositionUpdate) {
				$(window).bind("resize", {
							"noAnimation" : true,
							"formElem" : form
						}, methods.updatePromptsPosition);
			}
			form
					.on(
							"click",
							"a[data-validation-engine-skip], a[class*='validate-skip'], button[data-validation-engine-skip], button[class*='validate-skip'], input[data-validation-engine-skip], input[class*='validate-skip']",
							methods._submitButtonClick);
			form.removeData('jqv_submitButton');

			// bind form.submit
			form.on("submit", methods._onSubmitEvent);

			if (options.submitButtonID) {
				$("#" + options.submitButtonID).on("click", function() {
					var r = methods._onClickEvent(form);

					if (options.submitButtonFunc != null
							&& typeof options.submitButtonFunc == 'function') {
						options.submitButtonFunc(r);
					} else {
						alert("自定义submit函数[参见:submitButtonFunc:function(r){alert('r');}]有误,无法完成表单提交");
					}
				});
			}
			return this;
		},
		/**
		 * Unregisters any bindings that may point to jQuery.validaitonEngine
		 */
		detach : function() {

			var form = this;
			var options = form.data('jqv');

			// unbind fields
			form.find("[" + options.validateAttribute + "*=validate]")
					.not("[type=checkbox]").off(options.validationEventTrigger,
							methods._onFieldEvent);
			form
					.find("["
							+ options.validateAttribute
							+ "*=validate][type=checkbox],[class*=validate][type=radio]")
					.off("click", methods._onFieldEvent);

			// unbind form.submit
			form.off("submit", methods.onAjaxFormComplete);

			// unbind form.submit
			form.die("submit", methods.onAjaxFormComplete);
			form.removeData('jqv');

			form
					.off(
							"click",
							"a[data-validation-engine-skip], a[class*='validate-skip'], button[data-validation-engine-skip], button[class*='validate-skip'], input[data-validation-engine-skip], input[class*='validate-skip']",
							methods._submitButtonClick);
			form.removeData('jqv_submitButton');

			if (options.autoPositionUpdate)
				$(window).unbind("resize", methods.updatePromptsPosition);

			return this;
		},
		/**
		 * Validates either a form or a list of fields, shows prompts
		 * accordingly. Note: There is no ajax form validation with this method,
		 * only field ajax validation are evaluated
		 * 
		 * @return true if the form validates, false if it fails
		 */
		validate : function() {
			var element = $(this);
			var valid = null;

			if ((element.is("form") || element
					.hasClass("validationEngineContainer"))
					&& !element.hasClass('validating')) {
				element.addClass('validating');
				var options = element.data('jqv');
				var valid = methods._validateFields(this);

				// If the form doesn't validate, clear the 'validating' class
				// before the user has a chance to submit again
				setTimeout(function() {
							element.removeClass('validating');
						}, 100);
				if (valid && options.onSuccess) {
					options.onSuccess();
				} else if (!valid && options.onFailure) {
					options.onFailure();
				}
			} else if (element.is('form')
					|| element.hasClass('validationEngineContainer')) {
				element.removeClass('validating');
			} else {
				// field validation
				var form = element.closest('form, .validationEngineContainer'), options = (form
						.data('jqv'))
						? form.data('jqv')
						: $.validationEngine.defaults, valid = methods
						._validateField(element, options);

				if (valid && options.onFieldSuccess)
					options.onFieldSuccess();
				else if (options.onFieldFailure
						&& options.InvalidFields.length > 0) {
					options.onFieldFailure();
				}
			}
			if (options.onValidationComplete) {
				// !! ensures that an undefined return is interpreted as return
				// false but allows a onValidationComplete() to possibly return
				// true and have form continue processing
				return !!options.onValidationComplete(form, valid);
			}
			return valid;
		},
		/**
		 * Redraw prompts position, useful when you change the DOM state when
		 * validating
		 */
		updatePromptsPosition : function(event) {

			if (event && this == window) {
				var form = event.data.formElem;
				var noAnimation = event.data.noAnimation;
			} else
				var form = $(this.closest('form, .validationEngineContainer'));

			var options = form.data('jqv');
			// No option, take default one
			form.find('[' + options.validateAttribute + '*=validate]')
					.not(":disabled").each(function() {
						var field = $(this);
						if (options.prettySelect && field.is(":hidden"))
							field = form.find("#" + options.usePrefix
									+ field.attr('id') + options.useSuffix);
						var prompt = methods._getPrompt(field);
						var promptText = $(prompt).find(".formErrorContent")
								.html();

						if (prompt)
							methods._updatePrompt(field, $(prompt), promptText,
									undefined, false, options, noAnimation);
					});
			return this;
		},
		/**
		 * Displays a prompt on a element. Note that the element needs an id!
		 * 
		 * @param {String}
		 *            promptText html text to display type
		 * @param {String}
		 *            type the type of bubble: 'pass' (green), 'load' (black)
		 *            anything else (red)
		 * @param {String}
		 *            possible values topLeft, topRight, bottomLeft,
		 *            centerRight, bottomRight
		 */
		showPrompt : function(promptText, type, promptPosition, showArrow) {
			var form = this.closest('form, .validationEngineContainer');
			var options = form.data('jqv');
			// No option, take default one
			if (!options)
				options = methods._saveOptions(this, options);
			if (promptPosition)
				options.promptPosition = promptPosition;
			options.showArrow = showArrow == true;

			methods._showPrompt(this, promptText, type, false, options);
			return this;
		},
		/**
		 * Closes form error prompts, CAN be invidual
		 */
		hide : function() {
			var form = $(this).closest('form, .validationEngineContainer');
			var options = form.data('jqv');
			var fadeDuration = (options && options.fadeDuration)
					? options.fadeDuration
					: 0.3;
			var closingtag;

			if ($(this).is("form")
					|| $(this).hasClass("validationEngineContainer")) {
				closingtag = "parentForm"
						+ methods._getClassName($(this).attr("id"));
			} else {
				closingtag = methods._getClassName($(this).attr("id"))
						+ "formError";
			}
			$('.' + closingtag).fadeTo(fadeDuration, 0.3, function() {
						$(this).parent('.formErrorOuter').remove();
						$(this).remove();
					});
			return this;
		},
		/**
		 * Closes all error prompts on the page
		 */
		hideAll : function() {

			var form = this;
			var options = form.data('jqv');
			var duration = options ? options.fadeDuration : 300;
			$('.formError').fadeTo(duration, 300, function() {
						$(this).parent('.formErrorOuter').remove();
						$(this).remove();
					});
			return this;
		},
		/**
		 * Typically called when user exists a field using tab or a mouse click,
		 * triggers a field validation
		 */
		_onFieldEvent : function(event) {
			var field = $(this);
			var form = field.closest('form, .validationEngineContainer');
			var options = form.data('jqv');
			options.eventTrigger = "field";
			// validate the current field
			window.setTimeout(function() {
				methods._validateField(field, options);
				if (options.InvalidFields.length == 0 && options.onFieldSuccess) {
					options.onFieldSuccess();
				} else if (options.InvalidFields.length > 0
						&& options.onFieldFailure) {
					options.onFieldFailure();
				}
			}, (event.data) ? event.data.delay : 0);

		},
		/**
		 * Called when the form is submited, shows prompts accordingly
		 * 
		 * @param {jqObject}
		 *            form
		 * @return false if form submission needs to be cancelled
		 */
		_onSubmitEvent : function() {
			var form = $(this);
			var options = form.data('jqv');

			// check if it is trigger from skipped button
			if (form.data("jqv_submitButton")) {
				var submitButton = $("#" + form.data("jqv_submitButton"));
				if (submitButton) {
					if (submitButton.length > 0) {
						if (submitButton.hasClass("validate-skip")
								|| submitButton
										.attr("data-validation-engine-skip") == "true")
							return true;
					}
				}
			}

			options.eventTrigger = "submit";

			// validate each field
			// (- skip field ajax validation, not necessary IF we will perform
			// an ajax form validation)
			var r = methods._validateFields(form);

			if (r && options.ajaxFormValidation) {
				methods._validateFormWithAjax(form, options);
				// cancel form auto-submission - process with async call
				// onAjaxFormComplete
				return false;
			}

			if (options.onValidationComplete) {
				// !! ensures that an undefined return is interpreted as return
				// false but allows a onValidationComplete() to possibly return
				// true and have form continue processing
				return !!options.onValidationComplete(form, r);
			}
			return r;
		},
		_onClickEvent : function(form) {
			var options = form.data('jqv');

			// check if it is trigger from skipped button
			if (form.data("jqv_submitButton")) {
				var submitButton = $("#" + form.data("jqv_submitButton"));
				if (submitButton) {
					if (submitButton.length > 0) {
						if (submitButton.hasClass("validate-skip")
								|| submitButton
										.attr("data-validation-engine-skip") == "true")
							return true;
					}
				}
			}

			options.eventTrigger = "submit";

			// validate each field
			// (- skip field ajax validation, not necessary IF we will perform
			// an ajax form validation)
			var r = methods._validateFields(form);

			if (r && options.ajaxFormValidation) {
				methods._validateFormWithAjax(form, options);
				// cancel form auto-submission - process with async call
				// onAjaxFormComplete
				return false;
			}

			if (options.onValidationComplete) {
				// !! ensures that an undefined return is interpreted as return
				// false but allows a onValidationComplete() to possibly return
				// true and have form continue processing
				return !!options.onValidationComplete(form, r);
			}
			return r;
		},
		/**
		 * Return true if the ajax field validations passed so far
		 * 
		 * @param {Object}
		 *            options
		 * @return true, is all ajax validation passed so far (remember ajax is
		 *         async)
		 */
		_checkAjaxStatus : function(options) {
			var status = true;
			$.each(options.ajaxValidCache, function(key, value) {
						if (!value) {
							status = false;
							// break the each
							return false;
						}
					});
			return status;
		},

		/**
		 * Return true if the ajax field is validated
		 * 
		 * @param {String}
		 *            fieldid
		 * @param {Object}
		 *            options
		 * @return true, if validation passed, false if false or doesn't exist
		 */
		_checkAjaxFieldStatus : function(fieldid, options) {
			return options.ajaxValidCache[fieldid] == true;
		},
		/**
		 * Validates form fields, shows prompts accordingly
		 * 
		 * @param {jqObject}
		 *            form
		 * @param {skipAjaxFieldValidation}
		 *            boolean - when set to true, ajax field validation is
		 *            skipped, typically used when the submit button is clicked
		 * 
		 * @return true if form is valid, false if not, undefined if ajax form
		 *         validation is done
		 */
		_validateFields : function(form) {
			var options = form.data('jqv');

			// this variable is set to true if an error is found
			var errorFound = false;

			// Trigger hook, start validation
			form.trigger("jqv.form.validating");
			// first, evaluate status of non ajax fields
			var first_err = null;
			form.find('[' + options.validateAttribute + '*=validate]')
					.not(":disabled").each(function() {
						var field = $(this);
						var names = [];
						if ($.inArray(field.attr('name'), names) < 0) {
							errorFound |= methods
									._validateField(field, options);
							if (errorFound && first_err == null)
								if (field.is(":hidden") && options.prettySelect)
									first_err = field = form.find("#"
											+ options.usePrefix
											+ methods._jqSelector(field
													.attr('id'))
											+ options.useSuffix);
								else
									first_err = field;
							if (options.doNotShowAllErrosOnSubmit)
								return false;
							names.push(field.attr('name'));

							// if option set, stop checking validation rules
							// after one error is found
							if (options.showOneMessage == true && errorFound) {
								return false;
							}
						}
					});

			// second, check to see if all ajax calls completed ok
			// errorFound |= !methods._checkAjaxStatus(options);

			// third, check status and scroll the container accordingly
			form.trigger("jqv.form.result", [errorFound]);

			if (errorFound) {
				if (options.scroll) {
					var destination = first_err.offset().top;
					var fixleft = first_err.offset().left;

					// prompt positioning adjustment support. Usage:
					// positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or
					// bottomLeft:-20,+10)
					var positionType = options.promptPosition;
					if (typeof(positionType) == 'string'
							&& positionType.indexOf(":") != -1)
						positionType = positionType.substring(0, positionType
										.indexOf(":"));

					if (positionType != "bottomRight"
							&& positionType != "bottomLeft") {
						var prompt_err = methods._getPrompt(first_err);
						if (prompt_err) {
							destination = prompt_err.offset().top;
						}
					}

					// Offset the amount the page scrolls by an amount in px to
					// accomodate fixed elements at top of page
					if (options.scrollOffset) {
						destination -= options.scrollOffset;
					}

					// get the position of the first error, there should be at
					// least one, no need to check this
					// var destination =
					// form.find(".formError:not('.greenPopup'):first").offset().top;
					if (options.isOverflown) {
						var overflowDIV = $(options.overflownDIV);
						if (!overflowDIV.length)
							return false;
						var scrollContainerScroll = overflowDIV.scrollTop();
						var scrollContainerPos = -parseInt(overflowDIV.offset().top);

						destination += scrollContainerScroll
								+ scrollContainerPos - 5;
						var scrollContainer = $(options.overflownDIV
								+ ":not(:animated)");

						scrollContainer.animate({
									scrollTop : destination
								}, 1100, function() {
									if (options.focusFirstField)
										first_err.focus();
								});

					} else {
						$("html, body").animate({
									scrollTop : destination
								}, 1100, function() {
									if (options.focusFirstField)
										first_err.focus();
								});
						$("html, body").animate({
									scrollLeft : fixleft
								}, 1100)
					}

				} else if (options.focusFirstField)
					first_err.focus();
				return false;
			}
			return true;
		},
		/**
		 * This method is called to perform an ajax form validation. During this
		 * process all the (field, value) pairs are sent to the server which
		 * returns a list of invalid fields or true
		 * 
		 * @param {jqObject}
		 *            form
		 * @param {Map}
		 *            options
		 */
		_validateFormWithAjax : function(form, options) {

			var data = form.serialize();
			var type = (options.ajaxFormValidationMethod)
					? options.ajaxFormValidationMethod
					: "GET";
			var url = (options.ajaxFormValidationURL)
					? options.ajaxFormValidationURL
					: form.attr("action");
			var dataType = (options.dataType) ? options.dataType : "json";
			$.ajax({
				type : type,
				url : url,
				cache : false,
				dataType : dataType,
				data : data,
				form : form,
				methods : methods,
				options : options,
				beforeSend : function() {
					return options.onBeforeAjaxFormValidation(form, options);
				},
				error : function(data, transport) {
					methods._ajaxError(data, transport);
				},
				success : function(json) {
					if ((dataType == "json") && (json !== true)) {
						// getting to this case doesn't necessary means that the
						// form is invalid
						// the server may return green or closing prompt actions
						// this flag helps figuring it out
						var errorInForm = false;
						for (var i = 0; i < json.length; i++) {
							var value = json[i];

							var errorFieldId = value[0];
							var errorField = $($("#" + errorFieldId)[0]);

							// make sure we found the element
							if (errorField.length == 1) {

								// promptText or selector
								var msg = value[2];
								// if the field is valid
								if (value[1] == true) {

									if (msg == "" || !msg) {
										// if for some reason, status==true and
										// error="", just close the prompt
										methods._closePrompt(errorField);
									} else {
										// the field is valid, but we are
										// displaying a green prompt
										if (options.allrules[msg]) {
											var txt = options.allrules[msg].alertTextOk;
											if (txt)
												msg = txt;
										}
										if (options.showPrompts)
											methods._showPrompt(errorField,
													msg, "pass", false,
													options, true);
									}
								} else {
									// the field is invalid, show the red error
									// prompt
									errorInForm |= true;
									if (options.allrules[msg]) {
										var txt = options.allrules[msg].alertText;
										if (txt)
											msg = txt;
									}
									if (options.showPrompts)
										methods._showPrompt(errorField, msg,
												"", false, options, true);
								}
							}
						}
						options.onAjaxFormComplete(!errorInForm, form, json,
								options);
					} else
						options.onAjaxFormComplete(true, form, json, options);

				}
			});

		},
		/**
		 * Validates field, shows prompts accordingly
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            field's validation rules
		 * @param {Map}
		 *            user options
		 * @return false if field is valid (It is inversed for *fields*, it
		 *         return false on validate and true on errors.)
		 */
		_validateField : function(field, options, skipAjaxValidation) {
			if (!field.attr("id")) {
				field.attr("id", "form-validation-field-"
								+ $.validationEngine.fieldIdCounter);
				++$.validationEngine.fieldIdCounter;
			}

			if (!options.validateNonVisibleFields
					&& (field.is(":hidden") && !options.prettySelect || field
							.parent().is(":hidden")))
				return false;

			var rulesParsing = field.attr(options.validateAttribute);
			var getRules = /validate\[(.*)\]/.exec(rulesParsing);

			if (!getRules)
				return false;
			var str = getRules[1];
			var rules = str.split(/\[|,|\]/);

			// true if we ran the ajax validation, tells the logic to stop
			// messing with prompts
			var isAjaxValidator = false;
			var fieldName = field.attr("name");
			var promptText = "";
			var promptType = "";
			var required = false;
			var limitErrors = false;
			options.isError = false;
			options.showArrow = true;

			// If the programmer wants to limit the amount of error messages per
			// field,
			if (options.maxErrorsPerField > 0) {
				limitErrors = true;
			}

			var form = $(field.closest("form, .validationEngineContainer"));
			// Fix for adding spaces in the rules
			for (var i = 0; i < rules.length; i++) {
				rules[i] = rules[i].replace(" ", "");
				// Remove any parsing errors
				if (rules[i] === '') {
					delete rules[i];
				}
			}

			for (var i = 0, field_errors = 0; i < rules.length; i++) {

				// If we are limiting errors, and have hit the max, break
				if (limitErrors && field_errors >= options.maxErrorsPerField) {
					// If we haven't hit a required yet, check to see if there
					// is one in the validation rules for this
					// field and that it's index is greater or equal to our
					// current index
					if (!required) {
						var have_required = $.inArray('required', rules);
						required = (have_required != -1 && have_required >= i);
					}
					break;
				}

				var errorMsg = undefined;
				switch (rules[i]) {

					case "required" :
						required = true;
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._required);
						break;
					case "custom" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._custom);
						break;
					case "groupRequired" :
						// Check is its the first of group, if not, reload
						// validation with first field
						// AND continue normal validation on present field
						var classGroup = "[" + options.validateAttribute + "*="
								+ rules[i + 1] + "]";
						var firstOfGroup = form.find(classGroup).eq(0);
						if (firstOfGroup[0] != field[0]) {

							methods._validateField(firstOfGroup, options,
									skipAjaxValidation);
							options.showArrow = true;

						}
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options,
								methods._groupRequired);
						if (errorMsg)
							required = true;
						options.showArrow = false;
						break;
					case "ajax" :
						// AJAX defaults to returning it's loading message
						errorMsg = methods._ajax(field, rules, i, options);
						if (errorMsg) {
							promptType = "load";
						}
						break;
					case "minSize" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._minSize);
						break;
					case "maxSize" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._maxSize);
						break;
					case "min" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._min);
						break;
					case "max" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._max);
						break;
					case "past" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._past);
						break;
					case "future" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._future);
						break;
					case "dateRange" :
						var classGroup = "[" + options.validateAttribute + "*="
								+ rules[i + 1] + "]";
						options.firstOfGroup = form.find(classGroup).eq(0);
						options.secondOfGroup = form.find(classGroup).eq(1);

						// if one entry out of the pair has value then proceed
						// to run through validation
						if (options.firstOfGroup[0].value
								|| options.secondOfGroup[0].value) {
							errorMsg = methods._getErrorMessage(form, field,
									rules[i], rules, i, options,
									methods._dateRange);
						}
						if (errorMsg)
							required = true;
						options.showArrow = false;
						break;

					case "dateTimeRange" :
						var classGroup = "[" + options.validateAttribute + "*="
								+ rules[i + 1] + "]";
						options.firstOfGroup = form.find(classGroup).eq(0);
						options.secondOfGroup = form.find(classGroup).eq(1);

						// if one entry out of the pair has value then proceed
						// to run through validation
						if (options.firstOfGroup[0].value
								|| options.secondOfGroup[0].value) {
							errorMsg = methods._getErrorMessage(form, field,
									rules[i], rules, i, options,
									methods._dateTimeRange);
						}
						if (errorMsg)
							required = true;
						options.showArrow = false;
						break;
					case "maxCheckbox" :
						field = $(form.find("input[name='" + fieldName + "']"));
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options,
								methods._maxCheckbox);
						break;
					case "minCheckbox" :
						field = $(form.find("input[name='" + fieldName + "']"));
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options,
								methods._minCheckbox);
						break;
					case "equals" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._equals);
						break;
					case "funcCall" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options, methods._funcCall);
						break;
					case "creditCard" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options,
								methods._creditCard);
						break;
					case "condRequired" :
						errorMsg = methods._getErrorMessage(form, field,
								rules[i], rules, i, options,
								methods._condRequired);
						if (errorMsg !== undefined) {
							required = true;
						}
						break;

					default :
				}

				var end_validation = false;

				// If we were passed back an message object, check what the
				// status was to determine what to do
				if (typeof errorMsg == "object") {
					switch (errorMsg.status) {
						case "_break" :
							end_validation = true;
							break;
						// If we have an error message, set errorMsg to the
						// error message
						case "_error" :
							errorMsg = errorMsg.message;
							break;
						// If we want to throw an error, but not show a prompt,
						// return early with true
						case "_error_no_prompt" :
							return true;
							break;
						// Anything else we continue on
						default :
							break;
					}
				}

				// If it has been specified that validation should end now,
				// break
				if (end_validation) {
					break;
				}

				// If we have a string, that means that we have an error, so add
				// it to the error message.
				if (typeof errorMsg == 'string') {
					promptText += errorMsg + "<br/>";
					options.isError = true;
					field_errors++;
				}
			}
			// If the rules required is not added, an empty field is not
			// validated
			if (!required && !(field.val()) && field.val().length < 1)
				options.isError = false;

			// Hack for radio/checkbox group button, the validation go into the
			// first radio/checkbox of the group
			var fieldType = field.prop("type");
			var positionType = field.data("promptPosition")
					|| options.promptPosition;

			if ((fieldType == "radio" || fieldType == "checkbox")
					&& form.find("input[name='" + fieldName + "']").size() > 1) {
				if (positionType === 'inline') {
					field = $(form.find("input[name='" + fieldName
							+ "'][type!=hidden]:last"));
				} else {
					field = $(form.find("input[name='" + fieldName
							+ "'][type!=hidden]:first"));
				}
				options.showArrow = false;
			}

			if (field.is(":hidden") && options.prettySelect) {
				field = form.find("#" + options.usePrefix
						+ methods._jqSelector(field.attr('id'))
						+ options.useSuffix);
			}

			if (options.isError && options.showPrompts) {
				methods._showPrompt(field, promptText, promptType, false,
						options);
			} else {
				if (!isAjaxValidator)
					methods._closePrompt(field);
			}

			if (!isAjaxValidator) {
				field.trigger("jqv.field.result", [field, options.isError,
								promptText]);
			}

			/* Record error */
			var errindex = $.inArray(field[0], options.InvalidFields);
			if (errindex == -1) {
				if (options.isError)
					options.InvalidFields.push(field[0]);
			} else if (!options.isError) {
				options.InvalidFields.splice(errindex, 1);
			}

			methods._handleStatusCssClasses(field, options);

			/* run callback function for each field */
			if (options.isError && options.onFieldFailure)
				options.onFieldFailure(field);

			if (!options.isError && options.onFieldSuccess)
				options.onFieldSuccess(field);

			return options.isError;
		},
		/**
		 * Handling css classes of fields indicating result of validation
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            field's validation rules
		 * @private
		 */
		_handleStatusCssClasses : function(field, options) {
			/* remove all classes */
			if (options.addSuccessCssClassToField)
				field.removeClass(options.addSuccessCssClassToField);

			if (options.addFailureCssClassToField)
				field.removeClass(options.addFailureCssClassToField);

			/* Add classes */
			if (options.addSuccessCssClassToField && !options.isError)
				field.addClass(options.addSuccessCssClassToField);

			if (options.addFailureCssClassToField && options.isError)
				field.addClass(options.addFailureCssClassToField);
		},

		/***********************************************************************
		 * _getErrorMessage
		 * 
		 * @param form
		 * @param field
		 * @param rule
		 * @param rules
		 * @param i
		 * @param options
		 * @param originalValidationMethod
		 * @return {*}
		 * @private
		 */
		_getErrorMessage : function(form, field, rule, rules, i, options,
				originalValidationMethod) {
			// If we are using the custon validation type, build the index for
			// the rule.
			// Otherwise if we are doing a function call, make the call and
			// return the object
			// that is passed back.
			var rule_index = jQuery.inArray(rule, rules);
			if (rule === "custom" || rule === "funcCall") {
				var custom_validation_type = rules[rule_index + 1];
				rule = rule + "[" + custom_validation_type + "]";
				// Delete the rule from the rules array so that it doesn't try
				// to call the
				// same rule over again
				delete(rules[rule_index]);
			}
			// Change the rule to the composite rule, if it was different from
			// the original
			var alteredRule = rule;

			var element_classes = (field.attr("data-validation-engine"))
					? field.attr("data-validation-engine")
					: field.attr("class");
			var element_classes_array = element_classes.split(" ");

			// Call the original validation method. If we are dealing with dates
			// or checkboxes, also pass the form
			var errorMsg;
			if (rule == "future" || rule == "past" || rule == "maxCheckbox"
					|| rule == "minCheckbox") {
				errorMsg = originalValidationMethod(form, field, rules, i,
						options);
			} else {
				errorMsg = originalValidationMethod(field, rules, i, options);
			}

			// If the original validation method returned an error and we have a
			// custom error message,
			// return the custom message instead. Otherwise return the original
			// error message.
			if (errorMsg != undefined) {
				var custom_message = methods._getCustomErrorMessage($(field),
						element_classes_array, alteredRule, options);
				if (custom_message)
					errorMsg = custom_message;
			}
			return errorMsg;

		},
		_getCustomErrorMessage : function(field, classes, rule, options) {
			var custom_message = false;
			var validityProp = methods._validityProp[rule];
			// If there is a validityProp for this rule, check to see if the
			// field has an attribute for it
			if (validityProp != undefined) {
				custom_message = field
						.attr("data-errormessage-" + validityProp);
				// If there was an error message for it, return the message
				if (custom_message != undefined)
					return custom_message;
			}
			custom_message = field.attr("data-errormessage");
			// If there is an inline custom error message, return it
			if (custom_message != undefined)
				return custom_message;
			var id = '#' + field.attr("id");
			// If we have custom messages for the element's id, get the message
			// for the rule from the id.
			// Otherwise, if we have custom messages for the element's classes,
			// use the first class message we find instead.
			if (typeof options.custom_error_messages[id] != "undefined"
					&& typeof options.custom_error_messages[id][rule] != "undefined") {
				custom_message = options.custom_error_messages[id][rule]['message'];
			} else if (classes.length > 0) {
				for (var i = 0; i < classes.length && classes.length > 0; i++) {
					var element_class = "." + classes[i];
					if (typeof options.custom_error_messages[element_class] != "undefined"
							&& typeof options.custom_error_messages[element_class][rule] != "undefined") {
						custom_message = options.custom_error_messages[element_class][rule]['message'];
						break;
					}
				}
			}
			if (!custom_message
					&& typeof options.custom_error_messages[rule] != "undefined"
					&& typeof options.custom_error_messages[rule]['message'] != "undefined") {
				custom_message = options.custom_error_messages[rule]['message'];
			}
			return custom_message;
		},
		_validityProp : {
			"required" : "value-missing",
			"custom" : "custom-error",
			"groupRequired" : "value-missing",
			"ajax" : "custom-error",
			"minSize" : "range-underflow",
			"maxSize" : "range-overflow",
			"min" : "range-underflow",
			"max" : "range-overflow",
			"past" : "type-mismatch",
			"future" : "type-mismatch",
			"dateRange" : "type-mismatch",
			"dateTimeRange" : "type-mismatch",
			"maxCheckbox" : "range-overflow",
			"minCheckbox" : "range-underflow",
			"equals" : "pattern-mismatch",
			"funcCall" : "custom-error",
			"creditCard" : "pattern-mismatch",
			"condRequired" : "value-missing"
		},
		/**
		 * Required validation
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @param {bool}
		 *            condRequired flag when method is used for internal purpose
		 *            in condRequired check
		 * @return an error string if validation failed
		 */
		_required : function(field, rules, i, options, condRequired) {
			switch (field.prop("type")) {
				case "text" :
				case "password" :
				case "textarea" :
				case "file" :
				case "select-one" :
				case "select-multiple" :
				default :
					var field_val = $.trim(field.val());
					var dv_placeholder = $.trim(field
							.attr("data-validation-placeholder"));
					var placeholder = $.trim(field.attr("placeholder"));
					if ((!field_val)
							|| (dv_placeholder && field_val == dv_placeholder)
							|| (placeholder && field_val == placeholder)) {
						return options.allrules[rules[i]].alertText;
					}
					break;
				case "radio" :
				case "checkbox" :
					// new validation style to only check dependent field
					if (condRequired) {
						if (!field.attr('checked')) {
							return options.allrules[rules[i]].alertTextCheckboxMultiple;
						}
						break;
					}
					// old validation style
					var form = field
							.closest("form, .validationEngineContainer");
					var name = field.attr("name");
					if (form.find("input[name='" + name + "']:checked").size() == 0) {
						if (form.find("input[name='" + name + "']:visible")
								.size() == 1)
							return options.allrules[rules[i]].alertTextCheckboxe;
						else
							return options.allrules[rules[i]].alertTextCheckboxMultiple;
					}
					break;
			}
		},
		/**
		 * Validate that 1 from the group field is required
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_groupRequired : function(field, rules, i, options) {
			var classGroup = "[" + options.validateAttribute + "*="
					+ rules[i + 1] + "]";
			var isValid = false;
			// Check all fields from the group
			field.closest("form, .validationEngineContainer").find(classGroup)
					.each(function() {
								if (!methods._required($(this), rules, i,
										options)) {
									isValid = true;
									return false;
								}
							});

			if (!isValid) {
				return options.allrules[rules[i]].alertText;
			}
		},
		/**
		 * Validate rules
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_custom : function(field, rules, i, options) {
			var customRule = rules[i + 1];
			var rule = options.allrules[customRule];
			var fn;
			if (!rule) {
				alert("jqv:custom rule not found - " + customRule);
				return;
			}

			if (rule["regex"]) {
				var ex = rule.regex;
				if (!ex) {
					alert("jqv:custom regex not found - " + customRule);
					return;
				}
				var pattern = new RegExp(ex);

				if (!pattern.test(field.val()))
					return options.allrules[customRule].alertText;

			} else if (rule["func"]) {
				fn = rule["func"];

				if (typeof(fn) !== "function") {
					alert("jqv:custom parameter 'function' is no function - "
							+ customRule);
					return;
				}

				if (!fn(field, rules, i, options))
					return options.allrules[customRule].alertText;
			} else {
				alert("jqv:custom type not allowed " + customRule);
				return;
			}
		},
		/**
		 * Validate custom function outside of the engine scope
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_funcCall : function(field, rules, i, options) {
			var functionName = rules[i + 1];
			var fn;
			if (functionName.indexOf('.') > -1) {
				var namespaces = functionName.split('.');
				var scope = window;
				while (namespaces.length) {
					scope = scope[namespaces.shift()];
				}
				fn = scope;
			} else
				fn = window[functionName]
						|| options.customFunctions[functionName];
			if (typeof(fn) == 'function')
				return fn(field, rules, i, options);

		},
		/**
		 * Field match
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_equals : function(field, rules, i, options) {
			var equalsField = rules[i + 1];

			if (field.val() != $("#" + equalsField).val())
				return options.allrules.equals.alertText;
		},
		/**
		 * Check the maximum size (in characters)
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_maxSize : function(field, rules, i, options) {
			var max = rules[i + 1];
			var len = field.val().length;

			if (len > max) {
				var rule = options.allrules.maxSize;
				return rule.alertText + max + rule.alertText2;
			}
		},
		/**
		 * Check the minimum size (in characters)
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_minSize : function(field, rules, i, options) {
			var min = rules[i + 1];
			var len = field.val().length;

			if (len < min) {
				var rule = options.allrules.minSize;
				return rule.alertText + min + rule.alertText2;
			}
		},
		/**
		 * Check number minimum value
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_min : function(field, rules, i, options) {
			var min = parseFloat(rules[i + 1]);
			var len = parseFloat(field.val());

			if (len < min) {
				var rule = options.allrules.min;
				if (rule.alertText2)
					return rule.alertText + min + rule.alertText2;
				return rule.alertText + min;
			}
		},
		/**
		 * Check number maximum value
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_max : function(field, rules, i, options) {
			var max = parseFloat(rules[i + 1]);
			var len = parseFloat(field.val());

			if (len > max) {
				var rule = options.allrules.max;
				if (rule.alertText2)
					return rule.alertText + max + rule.alertText2;
				// orefalo: to review, also do the translations
				return rule.alertText + max;
			}
		},
		/**
		 * Checks date is in the past
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_past : function(form, field, rules, i, options) {

			var p = rules[i + 1];
			var fieldAlt = $(form.find("input[name='" + p.replace(/^#+/, '')
					+ "']"));
			var pdate;

			if (p.toLowerCase() == "now") {
				pdate = new Date();
			} else if (undefined != fieldAlt.val()) {
				if (fieldAlt.is(":disabled"))
					return;
				pdate = methods._parseDate(fieldAlt.val());
			} else {
				pdate = methods._parseDate(p);
			}
			var vdate = methods._parseDate(field.val());

			if (vdate > pdate) {
				var rule = options.allrules.past;
				if (rule.alertText2)
					return rule.alertText + methods._dateToString(pdate)
							+ rule.alertText2;
				return rule.alertText + methods._dateToString(pdate);
			}
		},
		/**
		 * Checks date is in the future
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_future : function(form, field, rules, i, options) {

			var p = rules[i + 1];
			var fieldAlt = $(form.find("input[name='" + p.replace(/^#+/, '')
					+ "']"));
			var pdate;

			if (p.toLowerCase() == "now") {
				pdate = new Date();
			} else if (undefined != fieldAlt.val()) {
				if (fieldAlt.is(":disabled"))
					return;
				pdate = methods._parseDate(fieldAlt.val());
			} else {
				pdate = methods._parseDate(p);
			}
			var vdate = methods._parseDate(field.val());

			if (vdate < pdate) {
				var rule = options.allrules.future;
				if (rule.alertText2)
					return rule.alertText + methods._dateToString(pdate)
							+ rule.alertText2;
				return rule.alertText + methods._dateToString(pdate);
			}
		},
		/**
		 * Checks if valid date
		 * 
		 * @param {string}
		 *            date string
		 * @return a bool based on determination of valid date
		 */
		_isDate : function(value) {
			var dateRegEx = new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:0?[1-9]|1[0-2])(\/|-)(?:0?[1-9]|1\d|2[0-8]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(0?2(\/|-)29)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$/);
			return dateRegEx.test(value);
		},
		/**
		 * Checks if valid date time
		 * 
		 * @param {string}
		 *            date string
		 * @return a bool based on determination of valid date time
		 */
		_isDateTime : function(value) {
			var dateTimeRegEx = new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1}$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^((1[012]|0?[1-9]){1}\/(0?[1-9]|[12][0-9]|3[01]){1}\/\d{2,4}\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1})$/);
			return dateTimeRegEx.test(value);
		},
		// Checks if the start date is before the end date
		// returns true if end is later than start
		_dateCompare : function(start, end) {
			return (new Date(start.toString()) < new Date(end.toString()));
		},
		/**
		 * Checks date range
		 * 
		 * @param {jqObject}
		 *            first field name
		 * @param {jqObject}
		 *            second field name
		 * @return an error string if validation failed
		 */
		_dateRange : function(field, rules, i, options) {
			// are not both populated
			if ((!options.firstOfGroup[0].value && options.secondOfGroup[0].value)
					|| (options.firstOfGroup[0].value && !options.secondOfGroup[0].value)) {
				return options.allrules[rules[i]].alertText
						+ options.allrules[rules[i]].alertText2;
			}

			// are not both dates
			if (!methods._isDate(options.firstOfGroup[0].value)
					|| !methods._isDate(options.secondOfGroup[0].value)) {
				return options.allrules[rules[i]].alertText
						+ options.allrules[rules[i]].alertText2;
			}

			// are both dates but range is off
			if (!methods._dateCompare(options.firstOfGroup[0].value,
					options.secondOfGroup[0].value)) {
				return options.allrules[rules[i]].alertText
						+ options.allrules[rules[i]].alertText2;
			}
		},
		/**
		 * Checks date time range
		 * 
		 * @param {jqObject}
		 *            first field name
		 * @param {jqObject}
		 *            second field name
		 * @return an error string if validation failed
		 */
		_dateTimeRange : function(field, rules, i, options) {
			// are not both populated
			if ((!options.firstOfGroup[0].value && options.secondOfGroup[0].value)
					|| (options.firstOfGroup[0].value && !options.secondOfGroup[0].value)) {
				return options.allrules[rules[i]].alertText
						+ options.allrules[rules[i]].alertText2;
			}
			// are not both dates
			if (!methods._isDateTime(options.firstOfGroup[0].value)
					|| !methods._isDateTime(options.secondOfGroup[0].value)) {
				return options.allrules[rules[i]].alertText
						+ options.allrules[rules[i]].alertText2;
			}
			// are both dates but range is off
			if (!methods._dateCompare(options.firstOfGroup[0].value,
					options.secondOfGroup[0].value)) {
				return options.allrules[rules[i]].alertText
						+ options.allrules[rules[i]].alertText2;
			}
		},
		/**
		 * Max number of checkbox selected
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_maxCheckbox : function(form, field, rules, i, options) {

			var nbCheck = rules[i + 1];
			var groupname = field.attr("name");
			var groupSize = form
					.find("input[name='" + groupname + "']:checked").size();
			if (groupSize > nbCheck) {
				options.showArrow = false;
				if (options.allrules.maxCheckbox.alertText2)
					return options.allrules.maxCheckbox.alertText + " "
							+ nbCheck + " "
							+ options.allrules.maxCheckbox.alertText2;
				return options.allrules.maxCheckbox.alertText;
			}
		},
		/**
		 * Min number of checkbox selected
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_minCheckbox : function(form, field, rules, i, options) {

			var nbCheck = rules[i + 1];
			var groupname = field.attr("name");
			var groupSize = form
					.find("input[name='" + groupname + "']:checked").size();
			if (groupSize < nbCheck) {
				options.showArrow = false;
				return options.allrules.minCheckbox.alertText + " " + nbCheck
						+ " " + options.allrules.minCheckbox.alertText2;
			}
		},
		/**
		 * Checks that it is a valid credit card number according to the Luhn
		 * checksum algorithm.
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_creditCard : function(field, rules, i, options) {
			// spaces and dashes may be valid characters, but must be stripped
			// to calculate the checksum.
			var valid = false, cardNumber = field.val().replace(/ +/g, '')
					.replace(/-+/g, '');

			var numDigits = cardNumber.length;
			if (numDigits >= 14 && numDigits <= 16 && parseInt(cardNumber) > 0) {

				var sum = 0, i = numDigits - 1, pos = 1, digit, luhn = new String();
				do {
					digit = parseInt(cardNumber.charAt(i));
					luhn += (pos++ % 2 == 0) ? digit * 2 : digit;
				} while (--i >= 0)

				for (i = 0; i < luhn.length; i++) {
					sum += parseInt(luhn.charAt(i));
				}
				valid = sum % 10 == 0;
			}
			if (!valid)
				return options.allrules.creditCard.alertText;
		},
		/**
		 * Ajax field validation
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return nothing! the ajax validator handles the prompts itself
		 */
		_ajax : function(field, rules, i, options) {

			var errorSelector = rules[i + 1];
			var rule = options.allrules[errorSelector];
			var extraData = rule.extraData;
			var extraDataDynamic = rule.extraDataDynamic;
			var data = {
				"fieldId" : field.attr("id"),
				"fieldValue" : field.val()
			};

			if (typeof extraData === "object") {
				$.extend(data, extraData);
			} else if (typeof extraData === "string") {
				var tempData = extraData.split("&");
				for (var i = 0; i < tempData.length; i++) {
					var values = tempData[i].split("=");
					if (values[0] && values[0]) {
						data[values[0]] = values[1];
					}
				}
			}

			if (extraDataDynamic) {
				var tmpData = [];
				var domIds = String(extraDataDynamic).split(",");
				for (var i = 0; i < domIds.length; i++) {
					var id = domIds[i];
					if ($(id).length) {
						var inputValue = field
								.closest("form, .validationEngineContainer")
								.find(id).val();
						var keyValue = id.replace('#', '') + '='
								+ escape(inputValue);
						data[id.replace('#', '')] = inputValue;
					}
				}
			}

			// If a field change event triggered this we want to clear the cache
			// for this ID
			if (options.eventTrigger == "field") {
				delete(options.ajaxValidCache[field.attr("id")]);
			}

			// If there is an error or if the the field is already validated, do
			// not re-execute AJAX
			if (!options.isError
					&& !methods
							._checkAjaxFieldStatus(field.attr("id"), options)) {
				$.ajax({
					type : options.ajaxFormValidationMethod,
					url : rule.url,
					cache : false,
					dataType : "json",
					data : data,
					field : field,
					rule : rule,
					methods : methods,
					options : options,
					beforeSend : function() {
					},
					error : function(data, transport) {
						methods._ajaxError(data, transport);
					},
					success : function(json) {

						// asynchronously called on success, data is the json
						// answer from the server
						var errorFieldId = json[0];
						// var errorField = $($("#" + errorFieldId)[0]);
						var errorField = $("#" + errorFieldId).eq(0);

						// make sure we found the element
						if (errorField.length == 1) {
							var status = json[1];
							// read the optional msg from the server
							var msg = json[2];
							if (!status) {
								// Houston we got a problem - display an red
								// prompt
								options.ajaxValidCache[errorFieldId] = false;
								options.isError = true;

								// resolve the msg prompt
								if (msg) {
									if (options.allrules[msg]) {
										var txt = options.allrules[msg].alertText;
										if (txt) {
											msg = txt;
										}
									}
								} else
									msg = rule.alertText;

								if (options.showPrompts)
									methods._showPrompt(errorField, msg, "",
											true, options);
							} else {
								options.ajaxValidCache[errorFieldId] = true;

								// resolves the msg prompt
								if (msg) {
									if (options.allrules[msg]) {
										var txt = options.allrules[msg].alertTextOk;
										if (txt) {
											msg = txt;
										}
									}
								} else
									msg = rule.alertTextOk;

								if (options.showPrompts) {
									// see if we should display a green prompt
									if (msg)
										methods._showPrompt(errorField, msg,
												"pass", true, options);
									else
										methods._closePrompt(errorField);
								}

								// If a submit form triggered this, we want to
								// re-submit the form
								if (options.eventTrigger == "submit")
									field.closest("form").submit();
							}
						}
						errorField.trigger("jqv.field.result", [errorField,
										options.isError, msg]);
					}
				});

				return rule.alertTextLoad;
			}
		},
		/**
		 * Common method to handle ajax errors
		 * 
		 * @param {Object}
		 *            data
		 * @param {Object}
		 *            transport
		 */
		_ajaxError : function(data, transport) {
			if (data.status == 0 && transport == null)
				alert("The page is not served from a server! ajax call failed");
			else if (typeof console != "undefined")
				console.log("Ajax error: " + data.status + " " + transport);
		},
		/**
		 * date -> string
		 * 
		 * @param {Object}
		 *            date
		 */
		_dateToString : function(date) {
			return date.getFullYear() + "-" + (date.getMonth() + 1) + "-"
					+ date.getDate();
		},
		/**
		 * Parses an ISO date
		 * 
		 * @param {String}
		 *            d
		 */
		_parseDate : function(d) {

			var dateParts = d.split("-");
			if (dateParts == d)
				dateParts = d.split("/");
			if (dateParts == d) {
				dateParts = d.split(".");
				return new Date(dateParts[2], (dateParts[1] - 1), dateParts[0]);
			}
			return new Date(dateParts[0], (dateParts[1] - 1), dateParts[2]);
		},
		/**
		 * Builds or updates a prompt with the given information
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {String}
		 *            promptText html text to display type
		 * @param {String}
		 *            type the type of bubble: 'pass' (green), 'load' (black)
		 *            anything else (red)
		 * @param {boolean}
		 *            ajaxed - use to mark fields than being validated with ajax
		 * @param {Map}
		 *            options user options
		 */
		_showPrompt : function(field, promptText, type, ajaxed, options,
				ajaxform) {
			var prompt = methods._getPrompt(field);
			// The ajax submit errors are not see has an error in the form,
			// When the form errors are returned, the engine see 2 bubbles, but
			// those are ebing closed by the engine at the same time
			// Because no error was found befor submitting
			if (ajaxform)
				prompt = false;
			// Check that there is indded text
			if ($.trim(promptText)) {
				if (prompt)
					methods._updatePrompt(field, prompt, promptText, type,
							ajaxed, options);
				else
					methods._buildPrompt(field, promptText, type, ajaxed,
							options);
			}
		},
		/**
		 * Builds and shades a prompt for the given field.
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {String}
		 *            promptText html text to display type
		 * @param {String}
		 *            type the type of bubble: 'pass' (green), 'load' (black)
		 *            anything else (red)
		 * @param {boolean}
		 *            ajaxed - use to mark fields than being validated with ajax
		 * @param {Map}
		 *            options user options
		 */
		_buildPrompt : function(field, promptText, type, ajaxed, options) {

			// create the prompt
			var prompt = $('<div>');
			prompt.addClass(methods._getClassName(field.attr("id"))
					+ "formError");
			// add a class name to identify the parent form of the prompt
			prompt.addClass("parentForm"
					+ methods._getClassName(field
							.closest('form, .validationEngineContainer')
							.attr("id")));
			prompt.addClass("formError");

			switch (type) {
				case "pass" :
					prompt.addClass("greenPopup");
					break;
				case "load" :
					prompt.addClass("blackPopup");
					break;
				default :
					/* it has error */
					// alert("unknown popup type:"+type);
			}
			if (ajaxed)
				prompt.addClass("ajaxed");

			// create the prompt content
			var promptContent = $('<div>').addClass("formErrorContent")
					.html(promptText).appendTo(prompt);

			// determine position type
			var positionType = field.data("promptPosition")
					|| options.promptPosition;

			// create the css arrow pointing at the field
			// note that there is no triangle on max-checkbox and radio
			if (options.showArrow) {
				var arrow = $('<div>').addClass("formErrorArrow");

				// prompt positioning adjustment support. Usage:
				// positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or
				// bottomLeft:-20,+10)
				if (typeof(positionType) == 'string') {
					var pos = positionType.indexOf(":");
					if (pos != -1)
						positionType = positionType.substring(0, pos);
				}

				switch (positionType) {
					case "bottomLeft" :
					case "bottomRight" :
						prompt.find(".formErrorContent").before(arrow);
						arrow
								.addClass("formErrorArrowBottom")
								.html('<div class="line1"><!-- --></div><div class="line2"><!-- --></div><div class="line3"><!-- --></div><div class="line4"><!-- --></div><div class="line5"><!-- --></div><div class="line6"><!-- --></div><div class="line7"><!-- --></div><div class="line8"><!-- --></div><div class="line9"><!-- --></div><div class="line10"><!-- --></div>');
						break;
					case "topLeft" :
					case "topRight" :
						arrow
								.html('<div class="line10"><!-- --></div><div class="line9"><!-- --></div><div class="line8"><!-- --></div><div class="line7"><!-- --></div><div class="line6"><!-- --></div><div class="line5"><!-- --></div><div class="line4"><!-- --></div><div class="line3"><!-- --></div><div class="line2"><!-- --></div><div class="line1"><!-- --></div>');
						prompt.append(arrow);
						break;
				}
			}
			// Add custom prompt class
			if (options.addPromptClass)
				prompt.addClass(options.addPromptClass);

			// Add custom prompt class defined in element
			var requiredOverride = field.attr('data-required-class');
			if (requiredOverride !== undefined) {
				prompt.addClass(requiredOverride);
			} else {
				if (options.prettySelect) {
					if ($('#' + field.attr('id')).next().is('select')) {
						var prettyOverrideClass = $('#'
								+ field.attr('id')
										.substr(options.usePrefix.length)
										.substring(options.useSuffix.length))
								.attr('data-required-class');
						if (prettyOverrideClass !== undefined) {
							prompt.addClass(prettyOverrideClass);
						}
					}
				}
			}

			prompt.css({
						"opacity" : 0
					});
			if (positionType === 'inline') {
				prompt.addClass("inline");
				if (typeof field.attr('data-prompt-target') !== 'undefined'
						&& $('#' + field.attr('data-prompt-target')).length > 0) {
					prompt.appendTo($('#' + field.attr('data-prompt-target')));
				} else {
					field.after(prompt);
				}
			} else {
				field.before(prompt);
			}

			var pos = methods._calculatePosition(field, prompt, options);
			prompt.css({
				'position' : positionType === 'inline'
						? 'relative'
						: 'absolute',
				"top" : pos.callerTopPosition,
				"left" : pos.callerleftPosition,
				"marginTop" : pos.marginTopSize,
				"opacity" : 0
			}).data("callerField", field);

			if (options.autoHidePrompt) {
				setTimeout(function() {
							prompt.animate({
										"opacity" : 0
									}, function() {
										prompt.closest('.formErrorOuter')
												.remove();
										prompt.remove();
									});
						}, options.autoHideDelay);
			}
			return prompt.animate({
						"opacity" : 0.87
					});
		},
		/**
		 * Updates the prompt text field - the field for which the prompt
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {String}
		 *            promptText html text to display type
		 * @param {String}
		 *            type the type of bubble: 'pass' (green), 'load' (black)
		 *            anything else (red)
		 * @param {boolean}
		 *            ajaxed - use to mark fields than being validated with ajax
		 * @param {Map}
		 *            options user options
		 */
		_updatePrompt : function(field, prompt, promptText, type, ajaxed,
				options, noAnimation) {

			if (prompt) {
				if (typeof type !== "undefined") {
					if (type == "pass")
						prompt.addClass("greenPopup");
					else
						prompt.removeClass("greenPopup");

					if (type == "load")
						prompt.addClass("blackPopup");
					else
						prompt.removeClass("blackPopup");
				}
				if (ajaxed)
					prompt.addClass("ajaxed");
				else
					prompt.removeClass("ajaxed");

				prompt.find(".formErrorContent").html(promptText);

				var pos = methods._calculatePosition(field, prompt, options);
				var css = {
					"top" : pos.callerTopPosition,
					"left" : pos.callerleftPosition,
					"marginTop" : pos.marginTopSize
				};

				if (noAnimation)
					prompt.css(css);
				else
					prompt.animate(css);
			}
		},
		/**
		 * Closes the prompt associated with the given field
		 * 
		 * @param {jqObject}
		 *            field
		 */
		_closePrompt : function(field) {
			var prompt = methods._getPrompt(field);
			if (prompt)
				prompt.fadeTo("fast", 0, function() {
							prompt.parent('.formErrorOuter').remove();
							prompt.remove();
						});
		},
		closePrompt : function(field) {
			return methods._closePrompt(field);
		},
		/**
		 * Returns the error prompt matching the field if any
		 * 
		 * @param {jqObject}
		 *            field
		 * @return undefined or the error prompt (jqObject)
		 */
		_getPrompt : function(field) {
			var formId = $(field).closest('form, .validationEngineContainer')
					.attr('id');
			var className = methods._getClassName(field.attr("id"))
					+ "formError";
			var match = $("." + methods._escapeExpression(className)
					+ '.parentForm' + formId)[0];
			if (match)
				return $(match);
		},
		/**
		 * Returns the escapade classname
		 * 
		 * @param {selector}
		 *            className
		 */
		_escapeExpression : function(selector) {
			return selector.replace(/([#;&,\.\+\*\~':"\!\^$\[\]\(\)=>\|])/g,
					"\\$1");
		},
		/**
		 * returns true if we are in a RTLed document
		 * 
		 * @param {jqObject}
		 *            field
		 */
		isRTL : function(field) {
			var $document = $(document);
			var $body = $('body');
			var rtl = (field && field.hasClass('rtl'))
					|| (field && (field.attr('dir') || '').toLowerCase() === 'rtl')
					|| $document.hasClass('rtl')
					|| ($document.attr('dir') || '').toLowerCase() === 'rtl'
					|| $body.hasClass('rtl')
					|| ($body.attr('dir') || '').toLowerCase() === 'rtl';
			return Boolean(rtl);
		},
		/**
		 * Calculates prompt position
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {jqObject}
		 *            the prompt
		 * @param {Map}
		 *            options
		 * @return positions
		 */
		_calculatePosition : function(field, promptElmt, options) {

			var promptTopPosition, promptleftPosition, marginTopSize;
			var fieldWidth = field.width();
			var fieldLeft = field.position().left;
			var fieldTop = field.position().top;
			var fieldHeight = field.height();
			var promptHeight = promptElmt.height();

			// is the form contained in an overflown container?
			promptTopPosition = promptleftPosition = 0;
			// compensation for the arrow
			marginTopSize = -promptHeight;

			// prompt positioning adjustment support
			// now you can adjust prompt position
			// usage: positionType:Xshift,Yshift
			// for example:
			// bottomLeft:+20 means bottomLeft position shifted by 20 pixels
			// right horizontally
			// topRight:20, -15 means topRight position shifted by 20 pixels to
			// right and 15 pixels to top
			// You can use +pixels, - pixels. If no sign is provided than + is
			// default.
			var positionType = field.data("promptPosition")
					|| options.promptPosition;
			var shift1 = "";
			var shift2 = "";
			var shiftX = 0;
			var shiftY = 0;
			if (typeof(positionType) == 'string') {
				// do we have any position adjustments ?
				if (positionType.indexOf(":") != -1) {
					shift1 = positionType.substring(positionType.indexOf(":")
							+ 1);
					positionType = positionType.substring(0, positionType
									.indexOf(":"));

					// if any advanced positioning will be needed (percents or
					// something else) - parser should be added here
					// for now we use simple parseInt()

					// do we have second parameter?
					if (shift1.indexOf(",") != -1) {
						shift2 = shift1.substring(shift1.indexOf(",") + 1);
						shift1 = shift1.substring(0, shift1.indexOf(","));
						shiftY = parseInt(shift2);
						if (isNaN(shiftY))
							shiftY = 0;
					};

					shiftX = parseInt(shift1);
					if (isNaN(shift1))
						shift1 = 0;

				};
			};

			switch (positionType) {
				default :
				case "topRight" :
					promptleftPosition += fieldLeft + fieldWidth - 30;
					promptTopPosition += fieldTop;
					break;

				case "topLeft" :
					promptTopPosition += fieldTop;
					promptleftPosition += fieldLeft;
					break;

				case "centerRight" :
					promptTopPosition = fieldTop + 4;
					marginTopSize = 0;
					promptleftPosition = fieldLeft + field.outerWidth(true) + 5;
					break;
				case "centerLeft" :
					promptleftPosition = fieldLeft - (promptElmt.width() + 2);
					promptTopPosition = fieldTop + 4;
					marginTopSize = 0;

					break;

				case "bottomLeft" :
					promptTopPosition = fieldTop + field.height() + 5;
					marginTopSize = 0;
					promptleftPosition = fieldLeft;
					break;
				case "bottomRight" :
					promptleftPosition = fieldLeft + fieldWidth - 30;
					promptTopPosition = fieldTop + field.height() + 5;
					marginTopSize = 0;
					break;
				case "inline" :
					promptleftPosition = 0;
					promptTopPosition = 0;
					marginTopSize = 0;
			};

			// apply adjusments if any
			promptleftPosition += shiftX;
			promptTopPosition += shiftY;

			return {
				"callerTopPosition" : promptTopPosition + "px",
				"callerleftPosition" : promptleftPosition + "px",
				"marginTopSize" : marginTopSize + "px"
			};
		},
		/**
		 * Saves the user options and variables in the form.data
		 * 
		 * @param {jqObject}
		 *            form - the form where the user option should be saved
		 * @param {Map}
		 *            options - the user options
		 * @return the user options (extended from the defaults)
		 */
		_saveOptions : function(form, options) {

			// is there a language localisation ?
			if ($.validationEngineLanguage)
				var allRules = $.validationEngineLanguage.allRules;
			else
				$
						.error("jQuery.validationEngine rules are not loaded, plz add localization files to the page");
			// --- Internals DO NOT TOUCH or OVERLOAD ---
			// validation rules and i18
			$.validationEngine.defaults.allrules = allRules;

			var userOptions = $.extend(true, {}, $.validationEngine.defaults,
					options);

			form.data('jqv', userOptions);
			return userOptions;
		},

		/**
		 * Removes forbidden characters from class name
		 * 
		 * @param {String}
		 *            className
		 */
		_getClassName : function(className) {
			if (className)
				return className.replace(/:/g, "_").replace(/\./g, "_");
		},
		/**
		 * Escape special character for jQuery selector
		 * http://totaldev.com/content/escaping-characters-get-valid-jquery-id
		 * 
		 * @param {String}
		 *            selector
		 */
		_jqSelector : function(str) {
			return str.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,
					'\\$1');
		},
		/**
		 * Conditionally required field
		 * 
		 * @param {jqObject}
		 *            field
		 * @param {Array[String]}
		 *            rules
		 * @param {int}
		 *            i rules index
		 * @param {Map}
		 *            user options
		 * @return an error string if validation failed
		 */
		_condRequired : function(field, rules, i, options) {
			var idx, dependingField;

			for (idx = (i + 1); idx < rules.length; idx++) {
				dependingField = jQuery("#" + rules[idx]).first();

				/*
				 * Use _required for determining wether dependingField has a
				 * value. There is logic there for handling all field types, and
				 * default value; so we won't replicate that here Indicate this
				 * special use by setting the last parameter to true so we only
				 * validate the dependingField on chackboxes and radio buttons
				 * (#462)
				 */
				if (dependingField.length
						&& methods._required(dependingField, ["required"], 0,
								options, true) == undefined) {
					/*
					 * We now know any of the depending fields has a value, so
					 * we can validate this field as per normal required code
					 */
					return methods._required(field, ["required"], 0, options);
				}
			}
		},

		_submitButtonClick : function(event) {
			var button = $(this);
			var form = button.closest('form, .validationEngineContainer');
			form.data("jqv_submitButton", button.attr("id"));
		}
	};

	/**
	 * Plugin entry point. You may pass an action as a parameter or a list of
	 * options. if none, the init and attach methods are being called. Remember:
	 * if you pass options, the attached method is NOT called automatically
	 * 
	 * @param {String}
	 *            method (optional) action
	 */
	$.fn.validationEngine = function(method) {

		var form = $(this);
		if (!form[0])
			return form; // stop here if the form does not exist

		if (typeof(method) == 'string' && method.charAt(0) != '_'
				&& methods[method]) {

			// make sure init is called once
			if (method != "showPrompt" && method != "hide"
					&& method != "hideAll")
				methods.init.apply(form);

			methods[method].apply(form, Array.prototype.slice
							.call(arguments, 1));
		} else if (typeof method == 'object' || !method) {

			// default constructor with or without arguments
			methods.init.apply(form, arguments);
			methods.attach.apply(form);
		} else {
			$.error('Method ' + method
					+ ' does not exist in jQuery.validationEngine');
		}
	};

	// LEAK GLOBAL OPTIONS
	$.validationEngine = {
		fieldIdCounter : 0,
		defaults : {

			// Name of the event triggering field validation
			validationEventTrigger : "blur",
			// Automatically scroll viewport to the first error
			scroll : true,
			// Focus on the first input
			focusFirstField : true,
			// Show prompts, set to false to disable prompts
			showPrompts : true,
			// Should we attempt to validate non-visible input fields contained
			// in the form? (Useful in cases of tabbed containers, e.g.
			// jQuery-UI tabs)
			validateNonVisibleFields : false,
			// Opening box position, possible locations are: topLeft,
			// topRight, bottomLeft, centerRight, bottomRight, inline
			// inline gets inserted after the validated field or into an element
			// specified in data-prompt-target
			promptPosition : "topRight",
			bindMethod : "bind",
			// internal, automatically set to true when it parse a _ajax rule
			inlineAjax : false,
			// if set to true, the form data is sent asynchronously via ajax to
			// the form.action url (get)
			ajaxFormValidation : false,
			// The url to send the submit ajax validation (default to action)
			ajaxFormValidationURL : false,
			// HTTP method used for ajax validation
			ajaxFormValidationMethod : 'get',
			// Ajax form validation callback method: boolean onComplete(form,
			// status, errors, options)
			// retuns false if the form.submit event needs to be canceled.
			onAjaxFormComplete : $.noop,
			// called right before the ajax call, may return false to cancel
			onBeforeAjaxFormValidation : $.noop,
			// Stops form from submitting and execute function assiciated with
			// it
			onValidationComplete : false,

			// Used when you have a form fields too close and the errors
			// messages are on top of other disturbing viewing messages
			doNotShowAllErrosOnSubmit : false,
			// Object where you store custom messages to override the default
			// error messages
			custom_error_messages : {},
			// true if you want to vind the input fields
			binded : true,
			// set to true, when the prompt arrow needs to be displayed
			showArrow : true,
			// did one of the validation fail ? kept global to stop further ajax
			// validations
			isError : false,
			// Limit how many displayed errors a field can have
			maxErrorsPerField : false,

			// Caches field validation status, typically only bad status are
			// created.
			// the array is used during ajax form validation to detect issues
			// early and prevent an expensive submit
			ajaxValidCache : {},
			// Auto update prompt position after window resize
			autoPositionUpdate : false,

			InvalidFields : [],
			onFieldSuccess : false,
			onFieldFailure : false,
			onSuccess : false,
			onFailure : false,
			validateAttribute : "class",
			addSuccessCssClassToField : "",
			addFailureCssClassToField : "",

			// Auto-hide prompt
			autoHidePrompt : false,
			// Delay before auto-hide
			autoHideDelay : 10000,
			// Fade out duration while hiding the validations
			fadeDuration : 0.3,
			// Use Prettify select library
			prettySelect : false,
			// Add css class on prompt
			addPromptClass : "",
			// Custom ID uses prefix
			usePrefix : "",
			// Custom ID uses suffix
			useSuffix : "",
			// Only show one message per error prompt
			showOneMessage : false
		}
	};
	$(function() {
				$.validationEngine.defaults.promptPosition = methods.isRTL()
						? 'topLeft'
						: "topRight"
			});
})(jQuery);
